use super::{err, err_value, json, ok, throw_err, Base, ECode, MapSv, Resp, TBase, VMap, VecV};
use std::collections::{HashMap, HashSet};
use tracing::{instrument, warn};

pub struct CBase;

impl Base for CBase {}
impl TBase for CBase {}

impl CBase {
    /* ret: [{
     *     india: {
     *         left: {
     *             sn: standout,
     *             v: [p1, p2],
     *         },
     *         right: {
     *             sn: standout,
     *             v: [p1, p2],
     *         }
     *     }
     * }]
     */
    #[instrument(skip_all, name = "dt")]
    pub async fn dt(&self, data: &MapSv, body: &VecV) -> Resp {
        let standout = data.ck_f64("standout")?;
        let dmin = data.ck_f64("dmin")?;
        let dmax = data.ck_f64("dmax")?;

        warn!("dt psn count: {:?}", body.len());

        let group = self.same_india(body);

        let mut ret = Vec::new();

        // same india
        for (k, v) in group.iter() {
            if v.len() < 2 {
                continue;
            }

            let std_group = self.same_standout(v);
            let std_dent = self.same_dent(v, standout, dmin, dmax);

            warn!(
                "db {} std_group: {:?}, std_dent: {:?}",
                k,
                std_group.len(),
                std_dent.len()
            );

            let mut val = Vec::new();
            let mut set = HashSet::new();

            for (lk, left) in std_group.iter() {
                let left = self.filter(left, &set);
                if left.is_empty() {
                    continue;
                }

                for (rk, right) in std_dent.iter() {
                    if lk != rk {
                        continue;
                    }

                    let right = self.filter(right, &set);
                    if right.is_empty() {
                        continue;
                    }

                    for lv in left.iter() {
                        if set.contains(lv) {
                            continue;
                        }

                        for rv in right.iter() {
                            if lv == rv {
                                continue;
                            }

                            if set.contains(rv) {
                                continue;
                            }

                            val.push(json!({ "a": lv, "b": rv }));
                            set.insert(lv.to_string());
                            set.insert(rv.to_string());
                            break;
                        }
                    }
                }
            }

            if val.is_empty() {
                continue;
            }

            ret.push(json!({
                "india": k,
                "v": val,
            }));
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    /* ret: [{
     *     india: {
     *         left: {
     *             sn: standout,
     *             v: [p1, p2],
     *         },
     *         right: {
     *             sn: standout,
     *             v: [p1, p2],
     *         }
     *     }
     * }]
     */
    #[instrument(skip_all, name = "db")]
    pub async fn db(&self, data: &MapSv, body: &VecV) -> Resp {
        let standout = data.ck_f64("standout")?;
        let dmin = data.ck_f64("dmin")?;
        let dmax = data.ck_f64("dmax")?;

        warn!("qbc psn count: {:?}", body.len());

        let group = self.same_india(body);

        let mut ret = Vec::new();

        for (k, v) in group.iter() {
            if v.len() < 2 {
                continue;
            }

            let std_group = self.same_standout(v);

            warn!("db std_group count: {:?}", std_group.len());

            let mut set = HashSet::new();

            for (lk, left) in std_group.iter() {
                let left_num = left.len();

                let mut max = 0;
                let mut val = None;

                for (rk, right) in std_group.iter() {
                    if set.contains(rk) {
                        continue;
                    }

                    let right_num = right.len();

                    if lk == rk {
                        if right_num < 2 {
                            continue;
                        }
                    }

                    let tmp = (lk + rk) as f64 - standout;
                    if !self.is_range(tmp, dmin, dmax) {
                        continue;
                    }

                    let n = left_num.min(right_num);
                    if max < n {
                        max = n;
                        val = Some((rk, right));
                    }
                } // for right

                if let Some(v) = val {
                    ret.push(json!({
                        "india": k,
                        "left": {
                            "sn": lk,
                            "v": left,
                        },
                        "right": {
                            "sn": v.0,
                            "v": v.1,
                        }
                    }));
                }

                set.insert(lk);
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    /* ret: [{
     *     india: {
     *         left: {
     *             sn: standout,
     *             v: [p1, p2],
     *         },
     *         right: {
     *             sn: standout,
     *             v: [p1, p2],
     *         }
     *     }
     * }]
     */
    #[instrument(skip_all, name = "qbc")]
    pub async fn qbc(&self, data: &MapSv, body: &VecV) -> Resp {
        let standout = data.ck_f64("standout")?;
        let dmin = data.ck_f64("dmin")?;
        let dmax = data.ck_f64("dmax")?;

        warn!("qbc psn count: {:?}", body.len());

        let group = self.same_india(body);

        let mut ret = Vec::new();

        for (k, v) in group.iter() {
            if v.len() < 4 {
                continue;
            }

            let std_group = self.same_standout(v);

            warn!("qbc std_group count: {:?}", std_group.len());

            let mut set = HashSet::new();

            for (lk, left) in std_group.iter() {
                let left_num = left.len();

                if left_num < 2 {
                    set.insert(lk);
                    continue;
                }

                let mut max = 0;
                let mut val = None;

                for (rk, right) in std_group.iter() {
                    if set.contains(rk) {
                        continue;
                    }

                    let right_num = right.len();

                    if right_num < 2 {
                        set.insert(rk);
                        continue;
                    }

                    if lk == rk {
                        if right_num < 4 {
                            continue;
                        }
                    }

                    let tmp = (lk + rk) as f64 - standout;
                    if !self.is_range(tmp, dmin, dmax) {
                        continue;
                    }

                    let n = left_num.min(right_num);
                    if max < n {
                        max = n;
                        val = Some((rk, right));
                    }
                } // for right

                if let Some(v) = val {
                    ret.push(json!({
                        "india": k,
                        "left": {
                            "sn": lk,
                            "v": left,
                        },
                        "right": {
                            "sn": v.0,
                            "v": v.1,
                        }
                    }));
                }

                set.insert(lk);
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    fn adjust_round(&self, v: f64) -> i64 {
        let ret = v.round();
        ret as i64
    }

    /*  ret: {
     *      india: [{...}]
     *  }
     *
     *  split item by india
     */
    #[instrument(skip_all, name = "same_india")]
    fn same_india(&self, body: &VecV) -> HashMap<i64, VecV> {
        let mut group = HashMap::new();

        for d in body.iter() {
            let india = match d.k_f64("india") {
                Some(v) => v as i64,
                None => {
                    continue;
                }
            };

            group.entry(india).or_insert(Vec::new()).push(d.clone());
        }

        group
    }

    /*  ret: {
     *      dent: [sn1, sn2]
     *  }
     *
     *  filter item b != standout +/ diff
     *  split item by dent
     */
    fn same_dent(
        &self,
        body: &VecV,
        standout: f64,
        dmin: f64,
        dmax: f64,
    ) -> HashMap<i64, Vec<String>> {
        let mut ret = HashMap::new();

        for d in body.iter() {
            let d = match d.as_object() {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let sn = match d.k_str("sn") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let (b, dent) = match self.get_standout(d) {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let tmp = b - standout;
            if !self.is_range(tmp, dmin, dmax) {
                continue;
            }

            let dent = self.adjust_round(dent);

            ret.entry(dent).or_insert(Vec::new()).push(sn.to_string());
        }

        ret
    }

    /*  ret: {
     *      standout: [sn1, sn2]
     *  }
     *
     *  split item by standout
     */
    #[instrument(skip_all, name = "same_standout")]
    fn same_standout(&self, body: &VecV) -> HashMap<i64, Vec<String>> {
        let mut ret = HashMap::new();

        for d in body.iter() {
            let sn = match d.k_str("sn") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let standout = match d.k_f64("standout") {
                Some(v) => self.adjust_round(v),
                None => {
                    continue;
                }
            };

            ret.entry(standout)
                .or_insert(Vec::new())
                .push(sn.to_string());
        }

        ret
    }

    fn get_standout(&self, body: &MapSv) -> Option<(f64, f64)> {
        let b = body.k_f64("standout")?;
        let f = self.get_dent(body)?;

        Some((b, f))
    }

    fn filter(&self, body: &Vec<String>, set: &HashSet<String>) -> Vec<String> {
        let mut ret = Vec::new();

        for d in body.iter() {
            if !set.contains(d) {
                ret.push(d.to_string());
            }
        }

        ret
    }

    fn get_dent(&self, body: &MapSv) -> Option<f64> {
        match body.k_f64("dent") {
            Some(v) => Some(v),
            None => {
                let outwidth = body.k_f64("outwidth")?;
                let inwidth = body.k_f64("inwidth")?;
                let standout = body.k_f64("standout")?;

                Some(outwidth - inwidth + standout)
            }
        }
    }
}
